﻿using System;
using System.Diagnostics;
using Adventure.Server.Manager;
using Neitzel;

namespace Adventure.Server.Library
{
    /// <summary>
    /// Enhanced SavedObject, including 
    /// - HeartBeat
    /// - Resets
    /// - Awarenes of surroundings (Not implemented, will be implemented together with Level)
    /// </summary>
    public class AdventureObject : SavedObject
    {
        /// <summary>
        /// Logger for all logging done in this class.
        /// </summary>
        private static readonly TraceSource Logger = new TraceSource(ServerConstants.ServerTraceSourceNameLibrary);

        #region Attributes

        /// <summary>
        /// Attribute name to save if a Reset is required.
        /// </summary>
        public const string NeedResetAttribute = "NeedReset";

        /// <summary>
        /// Attribute name ro save the time till a reset is done.
        /// </summary>
        public const string ResetTimeAttribute = "ResetTime";

        /// <summary>
        /// Attribute name for heart beat mask.
        /// </summary>
        public const string HeartBeatMaskAttribute = "HeartBeatMask";

        #endregion

        #region Central Timer

        /// <summary>
        /// Central HeartBeat timer - this is used if no special Heart Beat Timer is given (e.g. of Level) 
        /// </summary>
        public static HeartBeatTimer CentralHeartBeatTimer { get; private set; }

        /// <summary>
        /// Sets the central heart beat timer.
        /// </summary>
        /// <param name="timer"></param>
        public static void SetHeartBeatTimer(HeartBeatTimer timer)
        {
            if (CentralHeartBeatTimer == null)
            {
                Logger.Info((int)ServerTraceId.Library, "Setting Central HeartBeatTimmer!");
                CentralHeartBeatTimer = timer;
                return;
            }
            Logger.Error((int)ServerTraceId.Library, "Tried to set another CentraleHeartBeatTimer!");
        }

        /// <summary>
        /// HeartBeatTimer to use.
        /// </summary>
        protected HeartBeatTimer HeartBeatTimer {
            get { return _heartBeatTimer ?? CentralHeartBeatTimer; }
            set
            {
                // Check if a new HeartBeatTimer is set.
                if (HeartBeatTimer == value)
                    return;

                // Set the new HeartBeatTimer.
                if (HeartBeatActive) HeartBeatTimer.UnregisterObject(this);
                _heartBeatTimer = value;
                if (HeartBeatActive) HeartBeatTimer.RegisterObject(this);

            }
        }
        private HeartBeatTimer _heartBeatTimer;

        #endregion

        #region Lifetime

        /// <summary>
        /// Initialization of this instance.
        /// </summary>
        /// <param name="attributes">Object attributes to initialize this instance.</param>
        protected override void Init(ObjectAttributes attributes)
        {
            // Base class initialization
            base.Init(attributes);

            // Initialization from attributes.
            ResetTime = attributes.GetAttributeAsLong(ResetTimeAttribute) ?? 0;
            NeedReset = attributes.GetAttributeAsBool(NeedResetAttribute) ?? false;

            // Add the OnResetHeartBeat to HeartBeat event
            HeartBeat += OnResetHeartBeat;

            // Get the HeartBeatMask
            HeartBeatMask = attributes.GetAttributeAsInt(HeartBeatMaskAttribute) ?? HeartBeatReasons.None;
        }

        /// <summary>
        /// Prepare saving this object()
        /// </summary>
        protected override void PrepareSave()
        {
            base.PrepareSave();
            Attributes.SetAttribute(ResetTimeAttribute, ResetTime);
            Attributes.SetAttribute(HeartBeatMaskAttribute, HeartBeatMask);
        }

        /// <summary>
        /// Dispose this instance.
        /// </summary>
        /// <param name="disposing">Managed disposing?</param>
        /// <remarks>
        /// This is not fully disposing the instance. This is mainly used to take care of the 
        /// Reference Counter!
        /// </remarks>
        protected override void Dispose(bool disposing)
        {
            // Dispose base class
            base.Dispose(disposing);

            // Action to do if last reference is gone.
            if (disposing && ReferenceCounter == 0)
            {
                // Nothing to do so far.
            }
        }

        #endregion

        #region HeartBeat

        /// <summary>
        /// Mask with reasons why the Heartbeat is required.
        /// </summary>
        public int HeartBeatMask { get; set; }

        /// <summary>
        /// Gets / sets whether the HeartBeat is active or not?
        /// </summary>
        public bool HeartBeatActive => HeartBeatMask > 0;

        /// <summary>
        /// HeartBeat function which is called if the object should have a heart beat.
        /// </summary>
        public void ExecuteHeartBeat(HeartBeatTimer timer)
        {
            // Check that heart beat is active
            if (!HeartBeatActive)
                return;

            // Trigger the heart beat event.
            OnHeartBeat(timer, new EventArgs());
        }

        /// <summary>
        /// HeartBeat event.
        /// </summary>
        public event EventHandler<EventArgs> HeartBeat;

        /// <summary>
        /// Handling HeardBeat action / triggers the HeartBeat event.
        /// </summary>
        protected virtual void OnHeartBeat(object sender, EventArgs args)
        {
            Logger.Verbose((int)ServerTraceId.Library, "HeartBeat of {0}.", Id);
            var handler = HeartBeat;
            handler?.Invoke(sender, args);
        }

        /// <summary>
        /// Activates the HeartBeat for this instance.
        /// </summary>
        /// <param name="reason">Heart beat mask that gives reason.</param>
        public virtual void ActivateHeartBeat(int reason)
        {
            if (HeartBeatTimer != null)
            {
                Logger.Info((int)ServerTraceId.Library, "Activating HeartBeat in {0}", Id);
                HeartBeatMask |= reason;
                HeartBeatTimer.RegisterObject(this);
                return;
            }
            Logger.Error((int)ServerTraceId.Library, "Unable to activate HeartBeat - no HeartBeatTimer set!");
        }

        /// <summary>
        /// Deactivates the HeartBeat for this instance.
        /// </summary>
        public virtual void DeactivateHeartBeat()
        {
            HeartBeatTimer?.UnregisterObject(this);
        }

        #endregion

        #region Reset

        /// <summary>
        /// Gets or sets if the system needs a reset
        /// </summary>
        public bool NeedReset {
            get { return _timeToReset > 0; }
            set
            {
                if (value)
                {
                    _timeToReset = DateTime.Now.Ticks + ResetTime;
                    HeartBeatMask |= HeartBeatReasons.Reset;
                }
                else
                {
                    _timeToReset = -1;
                    HeartBeatMask &= ~HeartBeatReasons.Reset;
                }
            }
        }

        /// <summary>
        /// Gets or sets the time till a reset is done.
        /// </summary>
        public long ResetTime { get; set; }

        /// <summary>
        /// Time to the reset, -1 is disabled.
        /// </summary>
        private long _timeToReset = -1;

        /// <summary>
        /// Handler for HeartBeat to take care of a reset.
        /// </summary>
        /// <param name="sender">Sender of the event.</param>
        /// <param name="e">Event argument.</param>
        protected virtual void OnResetHeartBeat(object sender, EventArgs e)
        {
            // Check if we need a reset
            if (!NeedReset)
                return;

            if (DateTime.Now.Ticks > _timeToReset)
            {
                OnReset(this, new EventArgs());
            }
        }

        /// <summary>
        /// Reset event.
        /// </summary>
        public event EventHandler<EventArgs> Reset;
         
        /// <summary>
        /// Do the reset.
        /// </summary>
        protected virtual void OnReset(object sender, EventArgs e)
        {
            // Check that we need a reset.
            if (!NeedReset)
                return;

            // Invoke event.
            var handler = Reset;
            handler?.Invoke(sender, e);

            // We did a reset so we should not need one again.
            NeedReset = false;
        }

        #endregion
    }
}
